MFC 프로그래밍 – 김성엽
- MFC 공부를 시작하려면!
- MFC의 기본 클래스 소개 및 실습 준비하기
- MFC에서 메시지 처리하기
- 대화상자에컨트롤사용하기
- Edit 컨트롤에 값을 읽고 쓰는 다양한 방법에 대하여!
- ListBox 컨트롤을 사용하여 채팅 인터페이스 만들기
- 사각형을 마우스로 클릭해서 이동하기
- 투명한 윈도우 만들기
- 대화 상자 추가하기 (정형)
- 여러 개의 컨트롤에 입력된 값을 쉽게 관리하는 방법
- MFC에서 컨트롤 변수 직접 등록해서 사용하기
- [Q&A] MFC에서 윈도우 배경색 변경하기
- [실습 영상] MFC에서 사용자 정의 윈도우 만들기
- [실습 영상] 서브클래싱을 사용하여 버튼 기능 확장하기
- [MFC] List Box를 사용하여 색상 선택 기능 구현하기
- 시스템 전역 단축키 사용하기
- [MFC] 가격 계산 프로그램 만들기
- Edit 컨트롤의 색상 변경하기 – Step1
- 대화상자에서 메뉴 사용하기
- 대화 상자에서 단축키 사용하기
- 바이너리 뷰어 만들기 (CListBox 사용)
- 대화상자의 컨트롤중에서 Edit 컨트롤만 찾아서 문자열 설정하기
- 원 모양의 윈도우를 만들고 마우스로 이동하기
- 팝업 메뉴 사용하기 – Step1
- 탐색기에서 Drag And Drop된 파일 정보 사용하기 – Step1
- 파일 관리하기 – Step1 (목록구성)
- [MFC] 마우스 휠 버튼 사용하기 – Step 1
- [MFC] 키보드 방향 키로 사각형 움직이기 예제 – Step 1
- 네트워크 프로그래밍 – CSocket으로 정숫값 전달하기
유튜브 강의
https://www.youtube.com/watch?v=8MQsJDk5HTk&list=PLiZvlxkcLhalUHK9UnRS_KweH9R3tgBIO&index=2
김성엽의 MFC 이야기 (네이버 블로그)
https://blog.naver.com/tipsware/221307415937
2. MFC의 기본 클래스 소개 및 실습 준비하기
# InitInstance() 함수 정리
// 불필요한 코드가 추가되므로 아래처럼 4줄만 남기고 모두 지운다.
BOOL CExamMFC001App::InitInstance()
{
CWinApp::InitInstance();
CExamMFC001Dlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
3. MFC에서 메시지 처리하기
# 메시지 처리 함수 WindowProc 함수 추가하기
메뉴->프로젝트->클래스 마법사
클래스 이름 : CExamMFC001Dlg
가상 함수 : WindowProc
LRESULT CExamMFC001Dlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_LBUTTONDOWN)
{
// MFC 권장 코드
CClientDC dc(this);
dc.Rectangle(10, 10, 100, 100);
// Win32
//HDC h_dc = ::GetDC(m_hWnd);
//Rectangle(h_dc, 10, 10, 100, 100);
//::ReleaseDC(m_hWnd, h_dc);
// MFC 기본 코드
//CDC* p_dc = GetDC();
//p_dc->Rectangle(10, 10, 100, 100);
//ReleaseDC(p_dc);
//CDC 자식 클래스 CClientDC (다형성)
//CClientDC dc(this);
//CDC* p_dc = &dc;
//dc.Rectangle(10, 10, 100, 100);
}
return CDialogEx::WindowProc(message, wParam, lParam);
}
# 마우스 클릭 시 원이나 사각형 그리기
LRESULT CExamMFC001Dlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_LBUTTONDOWN)
{
CClientDC dc(this);
int x = LOWORD(lParam); // 하위 16비트 값 분리
int y = HIWORD(lParam); // 상위 16비트 값 분리
if (wParam & MK_CONTROL) dc.Ellipse(x - 30, y - 30, x + 30, y + 30);
else dc.Rectangle(x - 30, y - 30, x + 30, y + 30);
}
return CDialogEx::WindowProc(message, wParam, lParam);
}
// 추가한 가상 함수를 삭제하려면 CExamMFC001Dlg.h에서 함수 선언 삭제하고 CExamMFC001Dlg.cpp 에서 코드 삭제
# MFC 권장 방법 쓰기 (클래스 마법사에서 메시지 핸들러 추가하기)
메뉴->프로젝트->클래스 마법사
클래스 이름 : CExamMFC001Dlg
메시지 : WM_LBUTTONDOWN
// 3군데에 추가됨
1번째 (ExamMFC001Dlg.h)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
2번째 (ExamMFC001Dlg.cpp)
BEGIN_MESSAGE_MAP(CExamMFC001Dlg, CDialogEx)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
3번째 (ExamMFC001Dlg.cpp)
void CExamMFC001Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CDialogEx::OnLButtonDown(nFlags, point);
}
// 클래스 마법사에서 WM_LBUTTONDOWN 메시지를 삭제하면 주석 처리만 되고 코드는 남아있다.
// 그래서 직접 위 3군데를 찾아서 지워야 한다.
void CExamMFC001Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
if (nFlags & MK_CONTROL) dc.Ellipse(point.x - 30, point.y - 30, point.x + 30, point.y + 30);
else dc.Rectangle(point.x - 30, point.y - 30, point.x + 30, point.y + 30);
CDialogEx::OnLButtonDown(nFlags, point);
}
4. 대화상자에컨트롤사용하기
- 대화 상자에 에디트 컨트롤과 버튼 배치
- 버튼을 더블 클릭하면 메시지 핸들러가 자동으로 만들어진다.
void CExamMFC001Dlg::OnBnClickedShowMsgBtn()
{
//wchar_t str[64];
//GetDlgItemText(IDC_INPUT_MSG_EDIT, str, 64);
CString str, show_str;
GetDlgItemText(IDC_INPUT_MSG_EDIT, str);
//show_str.Format(_T("사용자가 입력한 문자열 : %s"), str);
//show_str = _T("사용자가 입력한 문자열 : ") + str;
AfxMessageBox(show_str);
}
# 리소스 ID 를 인식하지 못한다면 리소스 ID 에서 마우스 오른쪽 버튼을 누룬 후 Rescan – Rescan File 를 누룬다.
5. Edit 컨트롤에 값을 읽고 쓰는 다양한 방법에 대하여!
# 에디트 컨트롤->변수 추가
– 액세스 : protected
– 범주 : 값
– 이름 : m_my_string
# 읽기 버튼, 쓰기 버튼 두 개 배치
– 더블 클릭해서 메시지 처리기 생성
void CExamMFC001Dlg::OnBnClickedReadBtn() // 읽기 버튼 메시지 핸들러
{
UpdateData(TRUE); // 컨트롤 -> 변수
AfxMessageBox(m_my_string);
}
void CExamMFC001Dlg::OnBnClickedWriteBtn() // 쓰기 버튼 메시지 핸들러
{
m_my_string = _T("안녕하세요.");
UpdateData(FALSE);
}
# 에디트 컨트롤에서 변수 추가 한 것 삭제하려면 세 군데를 삭제한다.
### ExamMFC001Dlg.h
1.
protected:
CString m_my_string;
### ExamMFC001Dlg.cpp
2. CExamMFC001Dlg 클래스 생성자에서 변수 초기화 삭제
m_my_string(_T(""))
3.
void CExamMFC001Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT2, m_my_string); // 이 부분 삭제
}
# string -> int
int value = _ttoi(m_my_value)
# int -> string
CString str;
str.Format(_T(“%d”), m_my_string);
# 에디트 컨트롤에서 int 형 변수 추가
- – protected
– 값
– int
– m_my_value
# 에디트 컨트롤에서 int 값 읽기
void CExamMFC001Dlg::OnBnClickedReadBtn()
{
UpdateData(TRUE);
CString str;
str.Format(_T("%d"), m_my_value);
AfxMessageBox(str);
}
# 에디트 컨트롤에 값 쓰기
void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
m_my_value = 5;
UpdateData(FALSE);
}
# GetDlgItemText, SetDlgItemText
void CExamMFC001Dlg::OnBnClickedReadBtn()
{
CString str;
int len = GetDlgItemText(IDC_EDIT2, str);
str.Format(_T("%s (%d)"), str, len);
AfxMessageBox(str);
}
void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
SetDlgItemText(IDC_EDIT2, _T("반갑습니다."));
}
# GetDlgItemInt, SetDlgItemInt
void CExamMFC001Dlg::OnBnClickedReadBtn()
{
int value = GetDlgItemInt(IDC_EDIT2);
CString str;
str.Format(_T("%d"), value);
AfxMessageBox(str);
}
void CExamMFC001Dlg::OnBnClickedWriteBtn()
{
SetDlgItemInt(IDC_EDIT2, 5);
}
# GetDlgItem, SendMessage(WM_GETTEXTLENGTH)
void CExamMFC001Dlg::OnBnClickedReadBtn()
{
// CEdit 클래스 (CWnd는 부모 클래스) 다형성
//CWnd* p = GetDlgItem(IDC_EDIT2);
//CEdit* p_edit = (CEdit*)p;
// 이렇게 써도 됨.
//CEdit *p_edit = (CEdit*)GetDlgItem(IDC_EDIT2);
// CWnd 부모 클래스 사용 가능하므로.
CWnd* p = GetDlgItem(IDC_EDIT2);
int len = p->SendMessage(WM_GETTEXTLENGTH);
if (len > 3)
{
AfxMessageBox(_T("너무 길게 입력했습니다."));
}
else
{
wchar_t str[4];
GetDlgItemText(IDC_EDIT2, str, 4);
AfxMessageBox(str);
}
}
# p->SendMessage(WM_GETTEXTLENGTH); 함수를 좀 더 편하게 쓰기
int len = p->GetWindowTextLength();
// 내부적으로 p->SendMessage(WM_GETTEXTLENGTH); 를 포함.
# 좀 더 짧게 줄이기.
int len = GetDlgItem(IDC_EDIT2)->GetWindowTextLength();
if (GetDlgItem(IDC_EDIT2)->GetWindowTextLength() > 3)
6. ListBox 컨트롤을 사용하여 채팅 인터페이스 만들기
컨트롤 3개 배치
리스트 컨트롤 – IDC_CHAT_LIST
에디트 컨트롤 – IDC_CHAT_EDIT
버튼 – 추가, IDC_ADD_BTN
# 리스트 컨트롤에서 변수 추가
– 범주 : Control, 이름 : m_chat_list
# 버튼 더블클릭해서 메시지 핸들러 코드 생성
# 에디트 버튼에서 엔터키를 적용하려면 버튼의 Default Button 을 True 로 바꾸기 (기존 Default Button은 False 로 바꾼다.)
# 에디트 컨트롤에 엔터키가 적용이 안되었다면 서식(Format) -> 탭 순서(Tab Order) 에서 추가 버튼을 1번으로 바꾼다.
void CExamChatDlg::OnBnClickedAddBtn()
{
CString str;
GetDlgItemText(IDC_CHAT_EDIT, str);
SetDlgItemText(IDC_CHAT_EDIT, _T(""));
//m_chat_list.AddString(str); // 리스트 컨트롤 마지막에 추가
//m_chat_list.InsertString(0, str); // 인덱스 지정해서 추가
int index = m_chat_list.InsertString(-1, str); // -1은 마지막에 추가
m_chat_list.SetCurSel(index); // 추가된 아이템을 선택하고 자동 스크롤해줌.
}
7. 사각형을 마우스로 클릭해서 이동하기
# WM_PAINT
void CEXamMFC02Dlg::OnPaint()
{
CPaintDC dc(this); // else 문에도 써야하므로 여기로 빼준다.
if (IsIconic())
{
// 최소화 될 때 코드...
}
else
{
// 실제 코드는 여기다가 작성
// CRect r(10, 10, 100, 100);
CRect r;
r.SetRect(10, 10, 100, 100);
dc.Rectangle(r);
// CDialogEx::OnPaint(); // CPaintDC dc(this); 때문에 제거한다.
}
}
# 실제 예제
# CEXamMFC02Dlg.h
class CEXamMFC02Dlg : public CDialogEx
{
private:
CRect m_rect;
CPoint m_prev_pos;
char m_is_clicked = 0;
...
# CEXamMFC02Dlg.cpp
// 변수 초기화
CEXamMFC02Dlg::CEXamMFC02Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_EXAMMFC02_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_rect.SetRect(10, 10, 100, 100);
}
...
void CEXamMFC02Dlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
...
}
else
{
dc.Rectangle(m_rect);
}
}
...
// 클래스 마법사로 3개 메시지 핸들러 추가
void CEXamMFC02Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//if (point.x >= m_rect.left && point.y >= m_rect.top &&
// point.x <= m_rect.right && point.y <= m_rect.bottom)
if (m_rect.PtInRect(point)) // 간단한 함수 제공
{
m_is_clicked = 1;
m_prev_pos = point;
SetCapture(); // 마우스 포인터가 클라이언트 영역을 벗어나더라도 메시지를 받으라는 함수.
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CEXamMFC02Dlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_is_clicked == 1)
{
m_is_clicked = 0;
ReleaseCapture(); // 캡처 해제.
}
CDialogEx::OnLButtonUp(nFlags, point);
}
void CEXamMFC02Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_is_clicked == 1)
{
CPoint move_pos = point - m_prev_pos; // 연산자 오버로딩 되어 있음
//CPoint move_pos;
//move_pos.x = point.x - m_prev_pos.x;
//move_pos.y = point.y - m_prev_pos.y;
m_rect = m_rect + move_pos; // 연산자 오버로딩 되어 있음
//m_rect.left += move_pos.x;
//m_rect.top += move_pos.y;
//m_rect.right += move_pos.x;
//m_rect.bottom += move_pos.y;
m_prev_pos = point;
Invalidate();
}
CDialogEx::OnMouseMove(nFlags, point);
}
8. 투명한 윈도우 만들기
# 투명한 윈도우를 사용하려면 다이얼로그 속성 Layered 값에 True 를 준다.
BOOL CLayerExamDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 다이얼로그 Layered 속성을 True 로 하면 반드시 아래 둘 중 하나를 적용해야한다.
// 둘 다 안주면 완전 투명 윈도우로 나온다.
// 알파값 100을 적용 (0 ~ 255)
//SetLayeredWindowAttributes(0, 100, LWA_ALPHA);
// 임의로 한 색을 투명색으로 지정한다.
//SetLayeredWindowAttributes(RGB(255, 1, 7), 0, LWA_COLORKEY);
return TRUE; // return TRUE unless you set the focus to a control
}
void CLayerExamDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
...
}
else
{
dc.FillSolidRect(10, 10, 200, 200, RGB(255, 1, 7));
//CDialogEx::OnPaint();
}
}
void CLayerExamDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// Layered 속성 값이 False 이면 Layered 속성 적용
int wnd_style = GetWindowLong(m_hWnd, GWL_EXSTYLE);
if (!(wnd_style & WS_EX_LAYERED))
{
::SetWindowLong(m_hWnd, GWL_EXSTYLE, wnd_style | WS_EX_LAYERED);
}
SetLayeredWindowAttributes(RGB(255, 1, 7), 0, LWA_COLORKEY);
// 마우스 클릭하면 투명한 사각형 만들기
//CClientDC dc(this);
//dc.FillSolidRect(point.x - 10, point.y - 10, 20, 20, RGB(255, 1, 7));
CDialogEx::OnLButtonDown(nFlags, point);
}
9. 대화 상자 추가하기 (정형)
– 자식 대화상자 추가하기 –
- 리소스 뷰에서 다이얼로그 추가
- 대화상자에서 더블 클릭 -> MFC 클래스 추가 창이 뜬다. (새로 만든 대화상자는 클래스를 만들어 주어야 한다.)
- 클래스 이름 : NewDlg
- 클래스 마법사 -> 가상 함수 추가 : OninitDialog (대화상자가 생성된 후 화면에 나타나기 직전에 호출되는 함수, 초기값 세팅 작업을 한다.)
-
– 부모 대화상자 작업 –
- 버튼 추가 : IDC_NEW_DLG_BTN, 대화상자 실행
- 에디트 컨트롤 추가 : IDC_PARENT_NUM_EDIT
버튼 이벤트 핸들러
#include "NewDlg.h"
void CExamChatDlg::OnBnClickedNewDlgBtn()
{
int num = GetDlgItemInt(IDC_PARENT_NUM_EDIT);
NewDlg ins_dlg;
ins_dlg.SetNum(num);
// DoModal() 함수로 인해서 대화상자가 닫히면 대화상자와 그 안의 컨트롤들이 파괴됨.
// 따라서 미리 자식의 대화상자에서 값을 저장해 놓고 부모 대화상자에서 불러와야 한다.
int result = ins_dlg.DoModal();
// 버튼의 ID가 반환값이 된다.
if (IDOK == result)
{
num = ins_dlg.GetNum();
SetDlgItemInt(IDC_PARENT_NUM_EDIT, num);
}
else if (20 == result)
{
SetDlgItemInt(IDC_PARENT_NUM_EDIT, 0);
}
}
-
– 자식 대화상자 작업 –
확인 버튼 : 기존 사용
종료 버튼 추가 : IDC_EXIT_BTN
에디트 컨트롤 추가 : IDC_CHILD_NUM_EDIT
- NewDlg.h
class NewDlg : public CDialogEx
{
private:
int m_num; // 가능하면 private 으로 하라.
public:
void SetNum(int a_num)
{
m_num = a_num; // 부모 대화상자에서 사용
}
int GetNum()
{
return m_num; // 부모 대화상자에서 사용.
}
...
}
- NewDlg.cpp
BOOL NewDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
SetDlgItemInt(IDC_CHILD_NUM_EDIT, m_num); // 부모 대화상자에서 넘어온 값을 에디트 값에 넘겨준다.
return TRUE;
}
// 확인 버튼
void NewDlg::OnBnClickedOk()
{
m_num = GetDlgItemInt(IDC_CHILD_NUM_EDIT);
CDialogEx::OnOK(); // 내부적으로 EndDialog(IDOK);
}
// 종료 버튼
void NewDlg::OnBnClickedExitBtn()
{
// 반환값과 함께 종료
EndDialog(20);
}
10. 여러 개의 컨트롤에 입력된 값을 쉽게 관리하는 방법
컨트롤의 리소스 ID 를 순서대로 맞춰놓고 배열을 이용해서 값을 쉽게 관리한다.
# 에디트 컨트롤 5개
# 콤보박스 6개
Data : 1;2;3;4;5; (세미콜론으로 분리)
Type : Drop List (편집 막음)
# 버튼 1개
값 나열하기, IDC_SHOW_BTN
# 에디트 컨트롤 1개
IDC_NUM_EDIT
# Resource.h 파일
IDC_EDIT1 ~ IDC_EDIT5
IDC_COMBO1 ~ IDC_COMBO6
리소스 ID 가 순서대로 되어 있는지 확인.
원래 방법
void CExamChatDlg::OnBnClickedShowBtn()
{
int num[11], i;
for (i = 0; i < 5; i++) num[i] = GetDlgItemInt(IDC_EDIT1 + i);
CString str, total_str;
CComboBox* p_combo;
int index;
for (i = 0; i < 6; i++)
{
p_combo = (CComboBox*)GetDlgItem(IDC_COMBO1 + i); // CWnd 형을 반환하지만 다형성으로 자식인 CComboBox 받을 수 있다.
index = p_combo->GetCurSel();
if (index != CB_ERR)
{
p_combo->GetLBText(index, str);
num[5 + i] = _ttoi(str);
}
else
{
num[5 + i] = 0;
}
}
for (i = 0; i < 11; i++)
{
str.Format(_T("%d, "), num[i]);
total_str = total_str + str;
}
SetDlgItemText(IDC_NUM_EDIT, total_str);
}
리소스 ID 를 순서대로 맞춰 놓고 쉽게 하는 방법
void CExamChatDlg::OnBnClickedShowBtn()
{
int num[11], i;
CString str, total_str;
for (i = 0; i < 11; i++) num[i] = GetDlgItemInt(IDC_EDIT1 + i);
for (i = 0; i < 11; i++)
{
str.Format(_T("%d, "), num[i]);
total_str = total_str + str;
}
SetDlgItemText(IDC_NUM_EDIT, total_str);
}
11. MFC에서 컨트롤 변수 직접 등록해서 사용하기
# 클래스 마법사나 변수 추가를 사용하지 않고 관리하는 방법
1. 클래스에 컨트롤의 핸들로 Attach 하여 클래스가 컨트롤을 관리하도록 한다.
2. 직접 Attach 하는 경우 Detach 를 해서 클래스가 컨트롤을 관리하지 않도록 한다.
# WM_DESTORY
아직 윈도우는 존재하지만 대화상자가 곧 파괴되니까 마무리 작업 하라.
대화상자가 파괴될 때 대화상자도 컨트롤을 파괴하려고 하고 해당 클래스도 컨트롤을 파괴하려고 하기 때문에 충돌이 나서 문제가 된다.
반드시 Attach 를 했으면 Detach 를 해준다.
- NewDlg.h
private:
CEdit m_my_edit;
CListBox m_my_list_box;
- NewDlg.cpp
BOOL NewMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_my_edit.Attach(GetDlgItem(IDC_EDIT1)->m_hWnd);
m_my_list_box.Attach(GetDlgItem(IDC_LIST1)->m_hWnd);
return TRUE;
}
void NewMyDlg::OnDestroy()
{
CDialogEx::OnDestroy();
m_my_edit.Detach();
m_my_list_box.Detach();
}
void NewMyDlg::OnBnClickedButton1()
{
CString str;
m_my_edit.GetWindowText(str);
int index = m_my_list_box.InsertString(-1, str);
m_my_list_box.SetCurSel(index);
}
[Q&A] MFC에서 윈도우 배경색 변경하기
# SetDialogBkColor() 함수가 더이상 지원되지 않는다.
BOOL CLayerExamApp::InitInstance()
{
CWinApp::InitInstance();
// SetDialogBkColor(RGB(0, 200, 255), RGB(0, 0, 128)); // 더이상 지원 안함
CLayerExamDlg dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE;
}
# WM_ERASEBKGND (윈도우 리사이즈 등으로 배경을 지울 때 호출됨.)
# WM_CTLCOLOR (차일드 컨트롤이 그려지기 직전 호출됨.)
# CLayerExamDlg.h
class CLayerExamDlg : public CDialogEx
{
private:
CBrush m_bk_brush; // 브러시 하나 선언
# CLayerExamDlg.cpp
BOOL CLayerExamDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_bk_brush.CreateSolidBrush(RGB(0, 200, 255)); // 브러시를 하늘색으로 만든다. 브러시를 만들었으면 해제도 해준다. DeleteObject()
return TRUE;
}
void CLayerExamDlg::OnPaint()
{
if (IsIconic())
{
...
}
else
{
CPaintDC dc(this);
// Win32 API 이전 방식. 펜속성도 바꿔줘야 테두리 색도 바뀌므로 복잡하다.
//CRect r;
//GetClientRect(r);
//CBrush* p_old_brush = dc.SelectObject(&m_bk_brush);
//dc.Rectangle(r);
//dc.SelectObject(p_old_brush);
CRect r;
GetClientRect(r);
//dc.FillSolidRect(r, RGB(0, 200, 255)); // 브러시는 따로 만들었으므로 FillRect로 간다.
dc.FillRect(r, &m_bk_brush); // 직접 브러시로 적용할 때.
//CDialogEx::OnPaint();
}
}
void CLayerExamDlg::OnDestroy()
{
CDialogEx::OnDestroy();
m_bk_brush.DeleteObject();
}
# 클래스 마법사에서 WM_ERASEBKGND 메시지 핸들러를 생성 (색상이 달라지므로 권장 안함)
BOOL CLayerExamDlg::OnEraseBkgnd(CDC* pDC)
{
// 배경을 그리기 위한 메시지 핸들러
// WM_PAINT 보다 먼저 들어옴.
// BOOL flag = CDialogEx::OnEraseBkgnd(pDC); // 회색으로 채움 (하늘색과 중복 적용되기 때문에 지워준다.)
CRect r;
GetClientRect(r);
pDC->FillRect(r, &m_bk_brush); // 하늘색으로 채움
return TRUE; // 회색으로 채우지 않게 하고 TRUE로 적어주면 된다.
}
# WM_CTLCOLOR
- 대화상자나 컨트롤의 배경이나 전경을 바꿀 때 사용
HBRUSH CLayerExamDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
// 대화상자나 버튼 등의 컨트롤이 그려지기 전에 호출
// 배경색으로 쓸 브러시를 반환한다.
if (nCtlColor == CTLCOLOR_DLG) return m_bk_brush; // 버튼에 하늘색 배경색을 채움
else if (nCtlColor == CTLCOLOR_STATIC)
{
// 스태틱 컨트롤, 텍스트 배경은 바뀌지 않으므로 투명하게 바꿔준다.
//pDC->SetBkMode(TRANSPARENT); // 투명하게 하는건 부하가 좀 있으므로 텍스트 배경을 바꿔주는 방식으로 한다.
pDC->SetBkColor(RGB(0, 200, 255));
pDC->SetTextColor(RGB(255, 255, 255));
return (HBRUSH)::GetStockObject(NULL_BRUSH); // 하늘색 브러시보다는 배경 자체를 그리지 않게 한다.
}
return hbr;
}
# WM_CTLCOLOR 를 세분화해서 작업할 수 있다.
// WM_CTLCOLORMSGBOX, WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX, WM_CTLCOLORBTN, WM_CTLCOLORDLG, WM_CTLCOLORSCROLLBAR, WM_CTLCOLORSTATIC
// 클래스 마법사에 없으므로 직접 등록해서 사용해야 한다.
// 복잡하게 굳이 세분화해서 작업하기 보단 WM_CTLCOLOR 를 사용하는게 좋아 보인다.
# CLayerExamDlg.h
class CLayerExamDlg : public CDialogEx
{
LRESULT OnCtrlColorDlg(WPARAM wParam, LPARAM lParam);
# CLayerExamDlg.cpp
BEGIN_MESSAGE_MAP(CLayerExamDlg, CDialogEx)
ON_MESSAGE(WM_CTLCOLORDLG, OnCtrlColorDlg)
END_MESSAGE_MAP()
-----
LRESULT CLayerExamDlg::OnCtrlColorDlg(WPARAM wParam, LPARAM lParam)
{
//return (LRESULT)m_bk_brush.GetSafeHandle(); // GetSafeHandle()는 gdi 객체 핸들 반환. 이것도 괜찮음
return (LRESULT)(HBRUSH)m_bk_brush; // 연산자 오버로딩 되어 있음.
}
# WM_CTLCOLORSTATIC 직접 작성해보기 (스태틱 컨트롤 배경색 바꾸기)
// 3군데를 작성한다.
# 헤더 파일에 하나
LRESULT OnCtrlColorStatic(WPARAM wParam, LPARAM lParam);
# 소스 파일에 두 개
ON_MESSAGE(WM_CTLCOLORSTATIC, OnCtrlColorStatic)
LRESULT CLayerExamDlg::OnCtrlColorStatic(WPARAM wParam, LPARAM lParam)
{
CDC* dc = CDC::FromHandle((HDC)wParam);
HWND hwnd = (HWND)lParam;
dc->SetBkColor(RGB(0, 200, 255));
dc->SetTextColor(RGB(255, 255, 255));
return (LRESULT)(HBRUSH)::GetStockObject(NULL_BRUSH);
}
13. [실습 영상] MFC에서 사용자 정의 윈도우 만들기
클래스 마법사 -> Add Class -> MFC Class
클래스 이름 : UserWnd
기본 클래스 : CWnd
# 부모 다이얼로그 헤더 파일에 선언
#include “UserWnd.h”
Private:
UserWnd m_user_wnd;
# 부모 다이얼로그 소스 파일 OnInitDialog() 에 추가
//CRect를 사용하는 여러 방법
//CRect r;
//r.SetRect(50, 50, 200, 200);
//CRect r(50, 50, 200, 200);
m_user_wnd.Create(NULL, NULL, WS_VISIBLE | WS_CHILD | WS_BORDER, CRect(50, 50, 200, 200), this, 25000);
# UserWnd 클래스 소스 파일에 에 추가
void UserWnd::OnPaint()
{
CPaintDC dc(this);
CRect r;
GetClientRect(r);
dc.FillSolidRect(r, RGB(0, 0, 255)); // 배경색을 파란색으로
}
void UserWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect r;
r.SetRect(point.x - 10, point.y - 10, point.x + 10, point.y + 10);
CClientDC dc(this);
dc.FillSolidRect(r, RGB(255, 0, 0)); // 마우스 클릭 지점에 빨간 사각형 추가.
CWnd::OnLButtonDown(nFlags, point);
}
14. [실습 영상] 서브클래싱을 사용하여 버튼 기능 확장하기
기존의 윈도우나 컨트롤의 형태 또는 동작을 변경함
1. 새로운 클래스를 만들고 버튼에 서브클래싱한다.
2. 버튼을 누루면 새로운 클래스에 있는 메시지 핸들러 처리, 처리 후 부모 윈도우에 메시지 핸들러 처리.
# 서브클래싱을 하게 되면 서브클래싱된 새로운 클래스에 먼저 메시지가 전달된다.
# 컨트롤 배치
증가 버튼 : IDC_INC_BTN
감소 버튼 : IDC_DEC_BTN
에디트 컨트롤 : IDC_VALUE_EDIT
클래스 마법사 -> 클래스 추가 -> MFC 클래스 추가
클래스 이름: MyBtn
기본 클래스 : CButton
# MyBtn 클래스 (버튼 기능 확장) : 이 클래스에 있는 메시지 핸들러를 먼저 호출해서 처리 후 부모 윈도우에 있는 메시지 핸들러 처리
void MyBtn::OnLButtonDown(UINT nFlags, CPoint point)
{
SetTimer(1, 500, NULL);
CButton::OnLButtonDown(nFlags, point);
}
void MyBtn::OnLButtonUp(UINT nFlags, CPoint point)
{
KillTimer(1);
KillTimer(2); // 플래그성 메시지라 if로 확인 안하고 해도 됨. 중복 해제해도 가능. 문제 되지 않음.
CButton::OnLButtonUp(nFlags, point);
}
void MyBtn::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 1)
{
KillTimer(1);
SetTimer(2, 100, NULL);
}
else if (nIDEvent == 2)
{
GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), 20000), (LPARAM)m_hWnd); // 부모 윈도우에 WM_COMMAND 메시지를 보냄, MSND 참조
}
CButton::OnTimer(nIDEvent);
}
#include "MyBtn.h"
// CMyMFCDlg dialog
class CMyMFCDlg : public CDialogEx
{
private:
MyBtn m_inc_btn, m_dec_btn;
BOOL CMyMFCDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
SetDlgItemInt(IDC_VALUE_EDIT, 0);
m_inc_btn.SubclassDlgItem(IDC_INC_BTN, this); // IDC_INC_BTN 버튼에 서브클래싱을 했으므로 메시지가 m_inc_btn에 먼저 전달된다. 처리 후 아래 증가 버튼 처리
m_dec_btn.SubclassDlgItem(IDC_DEC_BTN, this);
return TRUE;
}
// 증가 버튼 클릭
void CMyMFCDlg::OnBnClickedIncBtn()
{
int value = GetDlgItemInt(IDC_VALUE_EDIT);
SetDlgItemInt(IDC_VALUE_EDIT, value + 1);
}
// 감소 버튼 클릭
void CMyMFCDlg::OnBnClickedDecBtn()
{
int value = GetDlgItemInt(IDC_VALUE_EDIT);
SetDlgItemInt(IDC_VALUE_EDIT, value - 1);
}
// 서브클래싱된 클래스에서 전달된 메시지 처리
BOOL CMyMFCDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (HIWORD(wParam) == 20000)
{
int value = GetDlgItemInt(IDC_VALUE_EDIT);
if (LOWORD(wParam) == IDC_INC_BTN) value++;
else value--;
SetDlgItemInt(IDC_VALUE_EDIT, value);
}
return CDialogEx::OnCommand(wParam, lParam);
}
# 다이얼로그에 있는 에디트 컨트롤은 WM_KEYDOWN 이 들지 않는다. 다음과 같이 작성해야 한다.
# 또는 서브클래싱 기능을 이용하면 WM_CHAR 또는 WM_KEYDOWN 을 받을 수 있다.
# 다음 예제는 Default Button 기능 때문에 엔터키를 눌렀을 때 프로그램이 종료되지 않게 하도록 만든다.
BOOL CMyMFCDlg::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN && pMsg->hwnd == GetDlgItem(IDC_EDIT1)->m_hWnd) {
if (pMsg->wParam == VK_RETURN) {
return TRUE;
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
MFC 서브 클래싱
# 정적 서브 클래싱 : 기존 컨트롤을 상속 받으면 자동으로 서브클래싱되는 방법
# 동적 서브 클래싱 : 동적으로 CWnd::SubclassWindow() 또는 CWnd::SubclassDlgItem() 이용하는 방법
15. [MFC] List Box를 사용하여 색상 선택 기능 구현하기
# 다이얼로그 배치
- 리스트 박스 1개, IDC_COLOR_LIST, Has String -> False, Ower Draw -> Fixed, Multicolmun -> True
- 라디오 버튼 2개 : 테두리 색상(IDC_PEN_RADIO), 채우기 색상(IDC_BRUSH_RADIO) -> Push Like 속성 True
# 클래스 마법사 -> 클래스 추가 -> MFC 클래스 추가
클래스 이름: CMyList
기본 클래스 : CListBox
# 헤더파일에 변수 추가
CMyList m_color_list;
afx_msg void OnLbnSelchangeColorList();
COLORREF m_pen_color = RGB(0, 0, 0), m_brush_color = RGB(255, 255, 255);
# 소스파일
BOOL CPenAndBrushDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
//CButton* p = (CButton*)GetDlgItem(IDC_PEN_RADIO);
//p->SetCheck(1);
((CButton*)GetDlgItem(IDC_PEN_RADIO))->SetCheck(1);
COLORREF color_table[20] = {
RGB(0,0,0),RGB(0,0,255),RGB(0,255,0),RGB(0,255,255),RGB(255,0,0),RGB(255,0,255),
RGB(255,255,0),RGB(255,255,255),RGB(0,0,128),RGB(0,128,0),RGB(0,128,128),RGB(128,0,0),
RGB(128,0,128),RGB(128,128,0),RGB(128,128,128),RGB(192,192,192),RGB(192,220,192),RGB(166,202,240),
RGB(255,251,240),RGB(160,160,164)
};
m_color_list.SubclassDlgItem(IDC_COLOR_LIST, this);
m_color_list.SetColumnWidth(30);
m_color_list.SetItemHeight(0, 30);
for (int i = 0; i < 20; i++)
{
m_color_list.InsertString(i, _T("하이"));
m_color_list.SetItemData(i, color_table[i]);
}
return TRUE;
}
void CPenAndBrushDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
...
}
else
{
CPen my_pen(PS_SOLID, 5, m_pen_color);
CPen *p_old_pen = dc.SelectObject(&my_pen); // 기존 펜 주소 반환
CBrush my_brush(m_brush_color);
CBrush* p_old_brush = dc.SelectObject(&my_brush);
dc.Rectangle(20, 20, 150, 150);
dc.SelectObject(p_old_pen); // 엠퍼센드(&) 넣지 말것
dc.SelectObject(p_old_brush);
my_pen.DeleteObject(); // 확실하게 하기위해서 delete 할 것.
my_brush.DeleteObject(); // 해제 순서는 상관 없음.
//CDialogEx::OnPaint();
}
}
// 리스트박스 LBN_SELCHANGE 이벤트 핸들러 추가
void CPenAndBrushDlg::OnLbnSelchangeColorList()
{
int index = m_color_list.GetCurSel();
if (LB_ERR != index) {
CButton* p = (CButton*)GetDlgItem(IDC_PEN_RADIO);
if (p->GetCheck()) m_pen_color = m_color_list.GetItemData(index);
else m_brush_color = m_color_list.GetItemData(index);
InvalidateRect(CRect(0,0,200,200)); // 화면 갱신 영역 지정 (깜빡임 제거 위해)
}
}
16. 시스템 전역 단축키 사용하기
# 헤더 파일에 변수 추가
bool m_show_flag = true;
# 소스 파일에 추가
// shift + pause 키를 시스템 전역 단축키로 등록 (26000번은 ID 식별자)
RegisterHotKey(m_hWnd, 26000, MOD_SHIFT, VK_PAUSE); // 등록하면 핫키를 누룰 때 WM_HOTKEY 가 발생
UnregisterHotKey(m_hWnd, 26000); // 해제
void CMFCShortcutKeyDlg::OnHotKey(UINT nHotKeyId, UINT nKey1, UINT nKey2)
{
// Shfit + Pause 키를 눌렀을 때 호출, ID 가 26000번이면
if (nHotKeyId == 26000)
{
if (m_show_flag == 1) ShowWindow(SW_HIDE);
else ShowWindow(SW_SHOW);
m_show_flag = !m_show_flag;
}
CDialogEx::OnHotKey(nHotKeyId, nKey1, nKey2);
}
17. [MFC] 가격 계산 프로그램 만들기
# 리스트박스 1개 추가 (체크리스트박스 클래스를 이용하여 서브클래싱)
– IDC_ITEM_LIST
– Owner Draw : Fixed (사용자가 정의해서 그리기)
– Has Strings : True (오너 드로우를 켰을 때 안하면 AddString 이나 InsertString 이 안먹힘.)
– CCheckListBox 변수 선언 후 리스트 박스를 서브 클래싱
// m_item_list.SubclassDlgItem(IDC_ITEM_LIST, this);
# 리스트박스 1개 추가
– IDC_COUNT_LIST
# 스태틱 컨트로 1개 추가
– 합산 가격
# 에디트 컨트롤 1개 추가
– IDC_TOTAL_PRICE_EDIT
# 스핀 컨트롤 1개 추가
– IDC_COUNT_SPIN
– Orientation : Horizontal
# 헤더 파일
#define MAX_ITEM_COUNT 8
class CCoffeeShopDlg : public CDialogEx
{
private:
CCheckListBox m_item_list;
CRect m_spin_rect;
public:
CCoffeeShopDlg(CWnd* pParent = nullptr); // standard constructor
void CalcTotalPrice();
void CCoffeeShopDlg::ChangeText(CListBox* ap_list_box, int a_index, const wchar_t* ap_string);
# 소스 파일
BOOL CCoffeeShopDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_count_spin.GetWindowRect(&m_spin_rect); // 윈도우 좌표 기준
ScreenToClient(&m_spin_rect); // 윈도우 좌표를 클라이언트 영역 좌표로 변경해줌.
wchar_t* p_item_name[MAX_ITEM_COUNT] = {
_T("아메리카노 1900원"), _T("카페라떼 2500원"),
_T("카페모카 2800원"), _T("카라멜마끼아또 3200원"),
_T("에스프레소 1800원"), _T("바닐라라떼 3500원"),
_T("카푸치노 3300원"), _T("비엔나 3500원"),
};
int price[8] = { 1900, 2500, 2800, 3200, 1800, 3500, 3300, 3500 };
m_item_list.SubclassDlgItem(IDC_ITEM_LIST, this);
// 체크박스가 깨지는 거 방지
m_item_list.SetItemHeight(0, 24);
m_count_list.SetItemHeight(0, 24);
for (int i = 0; i < MAX_ITEM_COUNT; i++)
{
m_item_list.InsertString(i, p_item_name[i]);
//SetItemData는 해당 아이템마다 4바이트 크기의 저장공간을 저장할 수 있음.
m_item_list.SetItemData(i, price[i]);
m_count_list.InsertString(i, _T("0"));
}
return TRUE;
}
void CCoffeeShopDlg::CalcTotalPrice()
{
int count = m_item_list.GetCount();
int total_price = 0;
CString str;
for (int i = 0; i < count; i++)
{
if (m_item_list.GetCheck(i))
{
m_count_list.GetText(i, str);
total_price += m_item_list.GetItemData(i) * _ttoi(str);
}
}
SetDlgItemInt(IDC_TOTAL_PRICE_EDIT, total_price);
}
// 첫번째 인자가 CListBox로 받으면 메뉴 리스트박스랑 카운트 리스트박스 모두 바꿀 수 있다.
void CCoffeeShopDlg::ChangeText(CListBox* ap_list_box, int a_index, const wchar_t* ap_string)
{
ap_list_box->DeleteString(a_index);
ap_list_box->InsertString(a_index, ap_string);
ap_list_box->SetCurSel(a_index); // 삭제하고 다시 삭제했으므로 다시 선택을 해주어야 한다.
}
void CCoffeeShopDlg::OnLbnSelchangeItemList()
{
int index = m_item_list.GetCurSel();
CString str;
m_count_list.GetText(index, str);
int item_count = _ttoi(str);
if (m_item_list.GetCheck(index)) {
if (item_count == 0) ChangeText(&m_count_list, index, _T("1"));
}
else {
if (item_count != 0) ChangeText(&m_count_list, index, _T("0"));
}
m_count_list.SetCurSel(index);
// SWP_NOSIZE: 폭과 높이를 변경 안함, 폭과 높이 파라미터를 0으로 적어도 변경 안함 // MoveWindow() 도 가능
m_count_spin.SetWindowPos(NULL, m_spin_rect.left, m_spin_rect.top + index * 24, 0, 0, SWP_NOSIZE);
CalcTotalPrice();
}
void CCoffeeShopDlg::OnDeltaposCountSpin(NMHDR* pNMHDR, LRESULT* pResult)
{
LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
*pResult = 0;
int index = m_item_list.GetCurSel();
if (LB_ERR != index && m_item_list.GetCheck(index))
{
CString str;
m_count_list.GetText(index, str);
int item_count = _ttoi(str);
if (pNMUpDown->iDelta > 0) {
if (item_count > 1) item_count--;
}
else {
if (item_count < 100)item_count++;
}
str.Format(_T("%d"), item_count);
ChangeText(&m_count_list, index, str);
CalcTotalPrice();
}
}
18. Edit 컨트롤의 색상 변경하기 – Step1
# 클래스 마법사 WM_CTLCOLOR 메시지 추가
# 헤더 파일
private:
HBRUSH mh_edit_bk_brush;
HWND mh_old_focus;
# 소스 파일
BOOL CExamEditDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
mh_edit_bk_brush = ::CreateSolidBrush(RGB(0, 0, 255)); // MFC 함수도 가능, :: 붙여서 API 32 함수를 쓰겠다.
for (int i = 0; i < 6; i++)
{
SetDlgItemText(IDC_EDIT1 + i, _T("안녕하세요."));
}
return TRUE;
}
HBRUSH CExamEditDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
int control_id = pWnd->GetDlgCtrlID();
if (control_id >= IDC_EDIT1 && control_id <= IDC_EDIT6)
{
HWND cur_focus = ::GetFocus(); // :: 붙여서 API32 함수를 써야 HWND 값을 반환한다.
if (cur_focus == pWnd->m_hWnd)
{
if (mh_old_focus != cur_focus)
{
if (mh_old_focus != NULL) ::InvalidateRect(mh_old_focus, NULL, TRUE); // WM_PAINT 를 발생하라.(윈도우 핸들, 전체 영역, 백그라운드)
mh_old_focus = cur_focus;
}
pDC->SetTextColor(RGB(0, 255, 255)); // 텍스트 색
}
else
{
pDC->SetTextColor(RGB(0, 168, 168)); // 텍스트 색
}
pDC->SetBkColor(RGB(0, 0, 128)); // 텍스트 배경색
return mh_edit_bk_brush; // 에디트 컨트롤 배경색
}
return hbr;
}
void CExamEditDlg::OnDestroy()
{
CDialogEx::OnDestroy();
DeleteObject(mh_edit_bk_brush);
}
19. 대화상자에서 메뉴 사용하기
# 컨트롤 배치
시작 버튼 : IDC_START_BTN
멈춤 버튼 : IDC_STOP_BTN
에디트 컨트롤 : IDC_STATE_BTN
# 메뉴 만들기
리소스 뷰 -> 리소스 추가 -> 메뉴 : IDR_MY_MENU
다이얼로그 메뉴에서 IDR_MY_MENU 선택해줌
기능(&O) – 시작(&S), 멈춤(&P), Separator, 종료(&X)
시작->마우스 오른쪽 버튼>이벤트 처리기 추가 (COMMAND 메시지 형식)
멈춤->IDC_STOP_BTN (멈춤 버튼과 ID 동일)
종료->ID_EXIT_PROGRAM
# 헤더 파일
private:
int m_start_flag = 0;
public:
void UpdateMenu();
# 소스 파일
BOOL CMFCMENUDlg::OnInitDialog()
{
OnBnClickedStopBtn();
}
void CMFCMENUDlg::OnBnClickedStartBtn()
{
m_start_flag = 1;
SetDlgItemText(IDC_STATE_EDIT, _T("시작했습니다."));
GetDlgItem(IDC_START_BTN)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP_BTN)->EnableWindow(TRUE);
UpdateMenu();
}
void CMFCMENUDlg::UpdateMenu()
{
CMenu* p_menu = GetMenu();
if (p_menu != NULL) {
CMenu* p_sub_menu = p_menu->GetSubMenu(0); // 첫번째 메뉴 (기능), 인덱스로 찾기
if (p_sub_menu != NULL)
{
if (m_start_flag == 1)
{
p_sub_menu->EnableMenuItem(ID_START_CMD, MF_BYCOMMAND | MF_DISABLED); // 커맨드 ID로 찾기
p_sub_menu->EnableMenuItem(IDC_STOP_BTN, MF_BYCOMMAND | MF_ENABLED);
}
else
{
p_sub_menu->EnableMenuItem(ID_START_CMD, MF_BYCOMMAND | MF_ENABLED);
p_sub_menu->EnableMenuItem(IDC_STOP_BTN, MF_BYCOMMAND | MF_DISABLED);
}
/* 짧게 코딩
p_sub_menu->EnableMenuItem(ID_START_CMD, m_start_flag * 2);
p_sub_menu->EnableMenuItem(IDC_STOP_BTN, (!m_start_flag) * 2);
*/
}
}
}
void CMFCMENUDlg::OnBnClickedStopBtn()
{
m_start_flag = 0;
SetDlgItemText(IDC_STATE_EDIT, _T("중지했습니다."));
GetDlgItem(IDC_START_BTN)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP_BTN)->EnableWindow(FALSE);
UpdateMenu();
}
void CMFCMENUDlg::OnStartCmd()
{
OnBnClickedStartBtn();
}
void CMFCMENUDlg::OnExitMenu()
{
EndDialog(IDOK);
}
20. 대화 상자에서 단축키 사용하기
# Accelerator 추가
리소스 뷰 -> 리소스 추가 -> Accelerator : IDR_MY_ACC
# 단축키 추가 : 오른쪽 버튼 > 다음 입력한 키
ctrl + c : ID_EDIT_CLEAR_CMD // 이벤트 처리기 추가
ctrl + s : ID_START_CMD (메뉴 이름)
ctrl + p : ID_STOP_BTN (버튼 이름)
클래스 마법사 -> 가상 함수 -> PreTranslateMessage
# 메뉴랑 단축키 ID를 동일하게 사용하는 경우 메뉴를 비활성시키면 단축키도 함께 비활성화 되므로 플래그 변수를 만들 필요 없다.
# 헤더 파일
private:
int m_start_flag = 0;
HACCEL m_acc_key;
# 소스 파일
BOOL CMFCMENUDlg::OnInitDialog()
{
m_acc_key = ::LoadAccelerators(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_MY_ACC)); // delete 안해도 됨, 정수형 리소스를 문자열로 바꾸기 매크로
OnBnClickedStopBtn();
}
BOOL CMFCMENUDlg::PreTranslateMessage(MSG* pMsg)
{
if (::TranslateAccelerator(m_hWnd, m_acc_key, pMsg)) return TRUE; // Accelerator에 단축키가 있다면 WM_COMMAND 메시지를 보냄
return CDialogEx::PreTranslateMessage(pMsg);
}
void CMFCMENUDlg::OnEditClearCmd()
{
SetDlgItemText(IDC_STATE_EDIT, _T(""));
}
21. 바이너리 뷰어 만들기 (CListBox 사용)
# 버튼 1개 : ID_SELECT_BTN
# 스태틱 컨트롤 1개 : IDC_PATH_STATIC (Sunken : True, Align Text : Center, Center Image : True, Caption : 선택한 파일이 없습니다.)
# 리스트 박스 1개 : IDC_BIN_DATA_BOX (변수 추가 : m_bin_data_list)
# 헤더 파일
private:
CFont m_font;
# 소스 파일
BOOL CMFCHexViewerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_font.CreatePointFont(128, _T("굴림체"));
m_bin_data_list.SetFont(&m_font);
}
void CMFCHexViewerDlg::OnBnClickedSelectBtn()
{
CFileDialog ins_dlg(TRUE); // 파일 열기 대화상자, FALSE : 다른 이름으로 저장 대화상자
if (IDOK == ins_dlg.DoModal())
{
m_bin_data_list.ResetContent();
SetDlgItemText(IDC_PATH_STATIC, ins_dlg.GetPathName());
FILE* p_file = NULL;
if (0 == _wfopen_s(&p_file, ins_dlg.GetPathName(), _T("rb")))
{
/*
CString total_str, str;
unsigned char temp[24];
int len = 24, line = 1;
while (len == 24)
{
len = fread(temp, 1, 24, p_file); // 1바이트씩 24개
if (len > 0)
{
total_str.Format(_T("%06d : "), line++); // total.Empty(); 또는 total_str = _T("");
for (int i = 0; i < 24; i++)
{
str.Format(_T("%02X "), temp[i]);
total_str += str; // 동적 메모리 할당으로 시스템 성능에 안좋다.
}
m_bin_data_list.InsertString(-1, total_str);
}
}
*/
wchar_t str[128];
unsigned char temp[24];
int len = 24, line = 1, str_len;
while (len == 24)
{
len = fread(temp, 1, 24, p_file); // 1바이트씩 24개
if (len > 0)
{
str_len = swprintf_s(str, 128, _T("%06d: "), line++);
for (int i = 0; i < 24; i++)
{
str_len += swprintf_s(str + str_len, 128 - str_len, _T("%02X "), temp[i]);
}
m_bin_data_list.InsertString(-1, str);
}
}
fclose(p_file); // _s 안붙이는 이유, 유니코드 문자가 안썼으므로
}
}
}
22. 대화상자의 컨트롤중에서 Edit 컨트롤만 찾아서 문자열 설정하기
# 버튼 4개와 에디트 컨트롤 4개를 만든다.
# 버튼 한개 만들기 (IDC_TEST_BTN, caption : 테스트)
# FindWindow() // 탑레벨 윈도우 찾기 (부모 윈도우만 찾음)
# FindWindowEx() // 자식 윈도우 찾기
# 소스 파일 (버튼 클릭이벤트)
void CMFCFindWindowDlg::OnBnClickedTestBtn()
{
/*
// 제일 간단하고, 제일 안정적이고, 중간에 임시 객체를 만들지 않고, Attach() Detach() 하지 않기 때문에 이 방법이 제일 좋음
// WIN 32 API 에 익숙하지 않다면 제일 아래에 있는 방법이 그나마 낫다.
// MFC 함수가 아닌 API32 함수이므로 :: 붙이기
// 결과는 찾은 컨트롤 핸들 (없으면 NULL)
// 첫번째 인자 : 윈도우 핸들, 두번째 : NULL은 처음부터 순서대로 찾기(핸들이면 그 다음부터 찾기), 클래스명(대소문자 구분 안함), ""이 아니라 NULL(문자열은 검색 조건 아님)
// 버튼 클래스명: button, 에디트 컨트롤 클래스명: edit
/*
HWND h_find_wnd = NULL;
while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
{
::SetWindowText(h_find_wnd, _T("Hello"));
}
*/
/* MFC 가 제공하는 함수 사용 (Cwnd* 반환)
* MFC의 함수 FindWindowEx 는 CWnd 임시 객체를 만들어서 임시객체의 주소를 반환한다.
HWND h_find_wnd = NULL;
CWnd* p_find_wnd = FindWindowEx(m_hWnd, NULL, _T("edit"), NULL);
while (p_find_wnd != NULL)
{
p_find_wnd->SetWindowTextW(_T("Hello"));
p_find_wnd = FindWindowEx(m_hWnd, p_find_wnd->m_hWnd, _T("edit"), NULL);
}
*/
/* 위 방법을 좀 더 간단하게
CWnd* p_find_wnd;
HWND h_find_wnd = NULL;
while (p_find_wnd = FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
{
p_find_wnd->SetWindowText(_T("Hello"));
h_find_wnd = p_find_wnd->m_hWnd;
}
*/
/*
// CWnd::FromHandle() 함수 사용
// CWnd::FromHandle(핸들) 핸들을 이용해서 CWnd 임시객체를 만들어서 임시객체의 주소를 반환.
CWnd* p_find_wnd;
HWND h_find_wnd = NULL;
while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
{
p_find_wnd = CWnd::FromHandle(h_find_wnd);
p_find_wnd->SetWindowText(_T("Hello"));
}
*/
// 첫번째 방법이 가장 낫지만 MFC 함수를 사용해야 한다면 그 다음으로 이 방법이 나음.
// CWnd 클래스는 WIN 32 API를 래핑해서 만듬.
// MFC의 CWnd 가 제공하는 FindWindowEx 나 FromHandle 은 MFC 임시객체를 만들어서 반환하기 때문에 많이 만들수록 성능이 좋지 않다.
// 임시객체를 만드는 방법보다는 Attach() Detach() 해서 사용해주는게 더 좋음, 멀티쓰레드 환경에서도 더 좋음.
// Attach() 윈도우 클래스랑 윈도우 핸들이랑 연결해줌.
// Detach() 는 분리해줌.
CWnd find_wnd; // 포인터로 만드는게 아니라 Cwnd 객체를 하나 만듦.
HWND h_find_wnd = NULL;
while (h_find_wnd = ::FindWindowEx(m_hWnd, h_find_wnd, _T("edit"), NULL))
{
find_wnd.Attach(h_find_wnd);
find_wnd.SetWindowText(_T("Hello"));
find_wnd.Detach();
}
}
23. 원 모양의 윈도우를 만들고 마우스로 이동하기
# 다이얼로그 속성
Title Bar : False
Border : None
# 클래스 마법사
WM_MOUSELBUTTONDOWN
WM_MOUSELBUTTONUP
WM_MOUSEMOVE
# 헤더 파일
private:
CPoint m_prev_pos;
char m_is_clicked = 0;
# 소스 파일
OnInitDialog()
CRgn rgn;
rgn.CreateEllipticRgn(0, 0, 200, 200);
SetWindowRgn(rgn, TRUE);
SetBackgroundColor(RGB(0, 200, 255));
void CEllipseTargetDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_is_clicked == 0) {
m_is_clicked = 1;
GetCursorPos(&m_prev_pos);
SetCapture();
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CEllipseTargetDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_is_clicked == 1) {
m_is_clicked = 0;
ReleaseCapture();
}
CDialogEx::OnLButtonUp(nFlags, point);
}
void CEllipseTargetDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_is_clicked == 1) {
CRect r;
GetWindowRect(r);
CPoint pos;
GetCursorPos(&pos);
// pos (현재 마우스 좌표) - m_prev_pos (이전 마우스 좌표) = 이동 거리
SetWindowPos(NULL, r.left + pos.x - m_prev_pos.x, r.top + pos.y - m_prev_pos.y, 0, 0, SWP_NOSIZE);
m_prev_pos = pos;
}
CDialogEx::OnMouseMove(nFlags, point);
}
24. 팝업 메뉴 사용하기 – Step1
# 리소스 뷰 -> 리소스 추가 -> 메뉴
IDR_MY_MENU
# 메뉴 추가
팝업 메뉴 : 열기(ID_MY_OPEN), 닫기(ID_MY_CLOSE)
# 클래스 마법사
메시지 : WM_RBUTTONUP
명령 : ID_MY_OPEN, ID_MY_CLOSE
void CEllipseTargetDlg::OnRButtonUp(UINT nFlags, CPoint point)
{
CMenu menu;
menu.LoadMenu(IDR_MY_MENU);
CMenu* p_sub_menu = menu.GetSubMenu(0); // 0번째 메뉴
CPoint pos;
GetCursorPos(&pos);
p_sub_menu->TrackPopupMenu(TPM_LEFTALIGN, pos.x, pos.y, this); // 전체화면 좌표
menu.DestroyMenu();
CDialogEx::OnRButtonUp(nFlags, point);
}
void CEllipseTargetDlg::OnMyOpen()
{
AfxMessageBox(_T("열기 선택"));
}
void CEllipseTargetDlg::OnMyClose()
{
AfxMessageBox(_T("닫기 선택"));
}
25. 탐색기에서 Drag And Drop된 파일 정보 사용하기 – Step1
# 다이얼로그 속성
Accept Files : True
# 리스트 박스 배치
IDC_DROP_LIST
변수 추가 : m_drop_list
# 클래스 마법사
WM_DROPFILES
void CEllipseTargetDlg::OnDropFiles(HDROP hDropInfo)
{
m_drop_list.ResetContent();
int count = DragQueryFile(hDropInfo, -1, NULL, 0); // 파일 개수 반환
wchar_t temp_path[MAX_PATH]; // 경로 최대값
for (int i = 0; i < count; i++)
{
DragQueryFile(hDropInfo, i, temp_path, MAX_PATH);
m_drop_list.InsertString(i, temp_path);
}
CDialogEx::OnDropFiles(hDropInfo);
}
26. 파일 관리하기 – Step1 (목록구성)
# 리스트박스 두 개 배치
IDC_LEFT_LIST
IDC_RIGHT_LIST
변수 추가 : m_left_list, m_right_list
BOOL CEllipseTargetDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 리스트 박스 기능
// 절대 경로, 상대 경로 다 됨 (..\\*.* 이전 경로)
// DDL_ARCHIVE | DDL_HIDDEN | DDL_DIRECTORY | DDL_DRIVES (파일, 숨김, 폴더, 드라이브)
m_left_list.Dir(DDL_ARCHIVE | DDL_HIDDEN | DDL_DIRECTORY, _T("*.*"));
// 일반적으로 파일 검색할 때
CString name;
WIN32_FIND_DATA file_data;
HANDLE h_item_list = FindFirstFile(_T("*.*"), &file_data);
if (h_item_list != INVALID_HANDLE_VALUE) { // 실패가 안하면
do {
// memcmp(file_data.cFileName, _T("."), 4);
// "." 디렉토리는 제외 (첫번째 문자가 '.' 이고, 두번째 문자가 0(NULL) 이면 제외)
// if (!(file_data.cFileName[0] == '.' && file_data.cFileName[1] == 0)) {
if (file_data.cFileName[0] != '.' || file_data.cFileName[1]) {
name = file_data.cFileName;
// 폴더에 대괄호 붙이기
if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) name = _T("[") + name + _T("]");
m_right_list.InsertString(-1, name);
}
} while (FindNextFile(h_item_list, &file_data));
FindClose(h_item_list);
}
return TRUE;
}
27. [MFC] 마우스 휠 버튼 사용하기 – Step 1
# 클래스 마법사
WM_MOUSEWHELL
# 헤더 파일
private:
int m_pos = 0;
# 소스 파일
void CEllipseTargetDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
if (IsIconic())
{
}
else
{
dc.FillSolidRect(10, 10, 30, 130, RGB(0, 0, 168));
dc.FillSolidRect(10, 10 + m_pos, 30, 30, RGB(0, 100, 228));
}
}
BOOL CEllipseTargetDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
int old_pos = m_pos;
if (zDelta < 0) {
if (m_pos < 100) m_pos++;
}
else {
if (m_pos > 0) m_pos--;
}
// 깜빡임 없애기.
if (old_pos != m_pos) InvalidateRect(CRect(10, 10, 40, 140), FALSE); // left, top, right, bottom, 배경을 지울건지 여부.
return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
28. [MFC] 키보드 방향 키로 사각형 움직이기 예제 – Step 1
# 대화상자 키보드는 컨트롤의 포커스를 옮기는 용도이기 때문에 WM_KEYDOWN 메시지가 안들어옴
# 대화상자가 이미 쓰고 있으므로 대화상자보다 먼저 처리해야 함.
# 클래스 마법사->가상 함수->PreTranslateMessage
// TranslateMessage 보다 먼저 호출
// 대화상자보다 먼저 메시지를 처리하겠다.
# 깜빡임 제거
대화상자 속성의 Clip Children 을 True 로 바꾼다.
// Clip Children : 부모 대화상자에서 자식 컨트롤 영역은 다시 그리지 않는다.
# 헤더 파일
private:
CRect m_rect;
int m_last_key_type = 0;
# 소스 파일
멤버변수 m_rect 초기화:
1. 생성자에서
CEllipseTargetDlg::CEllipseTargetDlg(CWnd* pParent) : CDialogEx(IDD_ELLIPSETARGET_DIALOG, pParent), m_rect(50, 50, 100, 100)
2. OnInitDialog() 에서
m_rect.SetRect(50, 50, 10, 100);
void CEllipseTargetDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
}
else
{
if (m_last_key_type) {
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(RGB(0, 200, 0));
dc.SelectObject(GetFont()); // 대화상자 폰트
if (m_last_key_type == VK_LEFT) {
dc.TextOut(m_rect.left - 15, m_rect.top + 19, _T("◀"), 1);
} else if (m_last_key_type == VK_UP) {
dc.TextOut(m_rect.left + 19, m_rect.top - 14, _T("▲"), 1);
} else if (m_last_key_type == VK_RIGHT) {
dc.TextOut(m_rect.right + 2, m_rect.top + 19, _T("▶"), 1);
} else if (m_last_key_type == VK_DOWN) {
dc.TextOut(m_rect.left + 19, m_rect.bottom + 1, _T("▼"), 1);
}
}
dc.FillSolidRect(m_rect, RGB(0, 255, 0));
}
}
BOOL CEllipseTargetDlg::PreTranslateMessage(MSG* pMsg)
{
// 모든 키가 다 들어오므로 필터링 처리
if (pMsg->message == WM_KEYDOWN) {
if (pMsg->wParam >= VK_LEFT && pMsg->wParam <= VK_DOWN) {
if (pMsg->wParam == VK_LEFT) {
m_rect.left--;
m_rect.right--;
}
else if (pMsg->wParam == VK_UP) {
m_rect.top--;
m_rect.bottom--;
}
else if (pMsg->wParam == VK_RIGHT) {
m_rect.left++;
m_rect.right++;
}
else if (pMsg->wParam == VK_DOWN) {
m_rect.top++;
m_rect.bottom++;
}
m_last_key_type = pMsg->wParam;
Invalidate();
return 1; // 컨트롤의 포커스도 같이 움직이므로 처리했다는 1을 리턴.
}
}
else if (pMsg->message == WM_KEYDOWN) {
if (pMsg->wParam >= VK_LEFT && pMsg->wParam <= VK_DOWN) {
m_last_key_type = 0;
Invalidate();
return 1;
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
29. 네트워크 프로그래밍 – CSocket으로 정숫값 전달하기
순서
- 서버에서 소켓 생성하고 Listen 상태로 만듦
- 클라이언트에서 Connect
- 접속이 성공하면 서버에서 OnAccept 함수로 인해서 CSocket 이 새로 만들어짐
- 서버에서 새로 만들어진 CSocket 으로 클라이언트와 Send, Receive 작업
# 프로젝트 2개 추가 : ValueServer, ValueClient
// 고급 기능 – Windows 소켓 체크
# 서버쪽 다이얼로그
에디트 컨트롤 1개 추가 : IDC_VALUE_EDIT
# 클라이언트쪽 다이얼로그
에디트 컨트롤 1개 추가 : IDC_VALUE_EDIT
버튼 1개 추가 : IDC_SEND_BTN, 전송
// 자기 IP 알기 : cmd -> ipconfig
< 서버쪽 작업 >
# Listen 하고 Accept 할 메인 소켓 만들기
클래스 마법사 -> MFC 클래스 추가
– 클래스 이름 : MyServer
– 기본 클래스 : CSocket
– 클래스 마법사 : 가상 함수 -> OnAccept 추가 // 클라이언트에서 Connect 하면 OnAccept 호출
# 접속이 성공하면 쓸 소켓 클래스 만들기
클래스 마법사 -> MFC 클래스 추가
– 기본 클래스 : CSocket
– 클래스 이름 : MyUser
– 클래스 마법사 -> 가상 함수 -> OnReceive 추가
# 클라이언트에서 접속을 해제하면 OnClose 함수 호출됨.
클래스 마법사 -> 가상 함수 -> OnClose 추가
// 서버쪽
# MyServer.h
#include "MyUser.h"
class MyServer : public CSocket
{
private:
MyUser m_user; // accept 성공하면 쓸 MFC 클래스
# MyServer.cpp (클래스 마법사 가상 함수로 OnAccept 추가)
void MyServer::OnAccept(int nErrorCode)
{
Accept(m_user);
CSocket::OnAccept(nErrorCode);
}
# MyUser.cpp (클래스 마법사 가상 함수로 OnReceive 와 OnClose 함수 추가)
void MyUser::OnReceive(int nErrorCode)
{
int value;
Receive(&value, sizeof(int));
// AfxGetMainWnd() = m_pMainWnd (메인 윈도우)
AfxGetMainWnd()->SetDlgItemInt(IDC_VALUE_EDIT, value);
value = 1;
Send(&value, sizeof(int)); // 잘 받았다는 확인 메시지 보냄
CSocket::OnReceive(nErrorCode);
}
void MyUser::OnClose(int nErrorCode)
{
ShutDown(2); // 읽기 쓰기 모두 해제.
Close();
CSocket::OnClose(nErrorCode);
}
# ValueServer (메인 다이얼로그)
BOOL CValueServerDlg::OnInitDialog()
{
// IP는 자동으로 적어주므로 포트번호만 적어주면 된다.
// IP 알아내기 : ipconfig
//m_server.Create(26001, 1, _T("192.168.0.2")); // 다 적어도 됨
m_server.Create(26001);
m_server.Listen(); // backlog 는 안적어도 됨.
}
< 클라이언트쪽 작업 >
# 클래스 마법사 MFC 클래스 추가
클래스 이름 : MyClient
기본 클래스 : CSocket
// Send 작업만 한다면 만들 필요없지만
// Receive 작업도 한다면 만들어야 함.
# 클라이언트
// 받기를 할 때는 MFC 클래스 추가로 CScoket 을 상속받아 OnReceive 를 사용해야 한다. (MyClient)
void MyClient::OnReceive(int nErrorCode)
{
int value;
Receive(&value, sizeof(int));
// AfxGetMainWnd() = m_pMainWnd (메인 윈도우)
AfxGetMainWnd()->SetDlgItemInt(IDC_VALUE_EDIT, value);
CSocket::OnReceive(nErrorCode);
}
# ValueClient.h (메인 다이얼로그)
#include "MyClient.h"
private:
MyClient m_client;
#ValueClient.cpp
BOOL CValueClientDlg::OnInitDialog()
{
m_client.Create();
m_client.Connect(_T("192.168.0.25"), 26001);
}
void CValueClientDlg::OnBnClickedSendBtn()
{
int value = GetDlgItemInt(IDC_VALUE_EDIT);
m_client.Send(&value, sizeof(int));
// 보내기만 할 때는 아래처럼 MyClient 클래스 없이 그냥 가능.
//CSocket temp;
//temp.Create();
//temp.Connect(_T("192. 168.0.25"), 26001);
//temp.Send(&value, sizeof(int)); // 4바이트 크기의 정수 값 전달
}